package de.sordul;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.GZIPInputStream;

import javax.xml.bind.DatatypeConverter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Static utility methods for smart card handling.
 */
public class SCUtils {
	final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();

	/**
	 * Returns a byte array for a given string representation. Example for a valid input string:<br>
	 * 00 A4 04 0C 06 D2 76 00 00 01 02
	 * 
	 * @param str Input string
	 * @return byte[] representation
	 */
	public static byte[] stringToBytes(String s) {
		return DatatypeConverter.parseHexBinary(s.replaceAll("\\s+", ""));
	}

	/**
	 * Returns an easily readable hex string representation of a byte array.
	 * 
	 * @param bytes Bytes to convert
	 * @return String with the given values
	 */
	static String bytesToString(byte[] bytes) {
		StringBuffer result = new StringBuffer();
		String[] resultChunks = DatatypeConverter.printHexBinary(bytes).split("(?<=\\G..)");

		for (int i = 0; i < resultChunks.length; i++) {
			result.append(resultChunks[i]);

			if (i + 1 != resultChunks.length) {
				result.append(" | ");
			}
		}

		return result.toString();
	}

	/**
	 * Convenience method to append byte[] chunks to an existing byte[] array.
	 * 
	 * @param currArray The current array to append things to
	 * @param newArray New items to append to current array
	 * @return The updated array
	 */
	static byte[] appendBytes(byte[] currArray, byte[] newArray) {
		List<Byte> tmpList = new ArrayList<Byte>();

		for (byte currByte : currArray) {
			tmpList.add(currByte);
		}

		for (byte currByte : newArray) {
			tmpList.add(currByte);
		}

		return SCUtils.toPrimitives(tmpList.toArray(new Byte[0]));
	}

	/**
	 * Converts a byte array to a GZIP stream, extracts an enveloped XML document and returns it.
	 * @param data GZipped XML document as byte array
	 * @return Document object
	 * @throws IOException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 */
	public static Document byteToXML(byte[] data) throws IOException, ParserConfigurationException, SAXException {
		
		// Build GZIP input stream on byte array
    	ByteArrayInputStream byteInStream = new ByteArrayInputStream(data);
    	ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
    	GZIPInputStream gZIPInputStream = new GZIPInputStream(byteInStream);
    	byte[] buffer = new byte[1024];
    	int bytes_read;
    	String xmlString;
    	Document doc;
    	
    	// Write content to an output stream and put it in a String
    	while ((bytes_read = gZIPInputStream.read(buffer)) > 0) {
    		byteOutStream.write(buffer, 0, bytes_read);
        }
    	gZIPInputStream.close();
    	xmlString = new String( byteOutStream.toByteArray(), java.nio.charset.StandardCharsets.UTF_8 );
    	
		// Create a document builder and convert the XML String into a document
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
	    DocumentBuilder builder = factory.newDocumentBuilder();
	    InputSource is = new InputSource(new StringReader(xmlString));
	    doc = builder.parse(is);	    		
	    doc.getDocumentElement().normalize();	    
	    
	    // Return the parsed document
		return doc;
	}

	/**
	 * Converts a Byte[] to byte[]
	 * 
	 * @param oBytes Original Byte[]
	 * @return Newly created byte[]
	 */
	private static byte[] toPrimitives(Byte[] oBytes) {

		byte[] bytes = new byte[oBytes.length];
		for (int i = 0; i < oBytes.length; i++) {
			bytes[i] = oBytes[i];
		}
		return bytes;

	}
}
